/* network.c equivalent for towerhack server */

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include <unistd.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <semaphore.h>
#include <stdbool.h>
#include "network.h"
#include "util/log.h"
#include "util/ipc.h"
#include "main.h"
#include "common/common_network.h"

#if TOWERHACK_DEBUG
#define DISC_TIMELIMIT (FPS*20)
#else
#define DISC_TIMELIMIT (FPS*6)
#endif
#define MSGLOG_SIZE 64
#define MAX_REPEAT_AT_ONCE 3

int sockhdl; /* socket handle */
int seq [MAX_CLIENTS]; /* sequence numbers for outgoing packets */
int seq_in [MAX_CLIENTS]; /* sequence numbers for incoming packets */
StoCmessage msglog [MAX_CLIENTS] [MSGLOG_SIZE];

bool connected = false;

/* initializes the socket. param is ignored */
void network_init (const char* servername)
{
	log_debug ("network_init (%s), message sizes are: StoC: %zd CtoS: %zd",
	           servername, sizeof(StoCmessage), sizeof(CtoSmessage));
	
	if (!connected) {
		int error;
		struct addrinfo hints, *bind_addr;
		memset(&hints, 0, sizeof hints);
		hints.ai_family = AF_UNSPEC; // set to AF_INET to force IPv4
		hints.ai_socktype = SOCK_DGRAM;
		hints.ai_flags = AI_PASSIVE; // use my IP

		if ((error = getaddrinfo(NULL, CTXSTR(SERVER_PORT), &hints, &bind_addr)) != 0) {
			log_fatal ("Failed to getaddrinfo on port %d: %s",
			           SERVER_PORT, gai_strerror(error));
		}
		
		sockhdl = socket(AF_INET, SOCK_DGRAM, 0);
		if (sockhdl < 0) {
			log_fatal ("Failed to create create server socket, errno=%d", errno);
		}
		/*
		struct sockaddr_in bind_addr;
		memset (&bind_addr, 0, sizeof(bind_addr));
		bind_addr.sin_family = AF_INET;
		bind_addr.sin_addr.s_addr = INADDR_ANY;
		bind_addr.sin_port = SERVER_PORT; */
		error = bind(sockhdl, (struct sockaddr *) bind_addr->ai_addr,
		             bind_addr->ai_addrlen);
		if (error != 0) {
			log_fatal ("Failed to bind server socket (port %d).", SERVER_PORT);
		}
		
		freeaddrinfo (bind_addr);
		memset (seq, 0, sizeof(seq));
		memset (seq_in, 0, sizeof(seq_in));
		memset (msglog, 0, sizeof(msglog));
		memset (the_network.client, 0, sizeof(the_network.client));
		memset (the_network.player, 0, sizeof(the_network.player));
		memset (the_network.owner,  0, sizeof(the_network.owner));
		the_network.n_clients = 0;
		the_network.n_players = 0;
		
		connected = true;
		ipc_wake_client (); // client on localhost may be waiting to connect
	}
}

void remove_client (int index)
{
	assert((index >= 0) && (index < the_network.n_clients));
	
	int i;
	StoCmessage message;
	memset (&message, 0, sizeof (message));
	
	log_debug ("remove_client (index %d)", index);
	
	if (notify_clientleave != 0) notify_clientleave (index);
	
	for (i = 0; i < the_network.n_players; i++) {
		if (the_network.owner[i] == index) {
			the_network.player[i].enabled = false;
		}
	}
	memset (&the_network.client[index], 0, sizeof (Client));
	
	message.type = StoC_LOBBY_CLIENTLEAVE;
	message.clientidx = index;
	send_StoCmessage (&message, -1);
}

void kick_client (int index)
{
	StoCmessage kickmsg;
	memset(&kickmsg, 0, sizeof(kickmsg));
	kickmsg.type = StoC_KICK;
	send_StoCmessage (&kickmsg, index);
	remove_client (index);
}

void network_update ()
{
	int active_clients = 0;
	for (int i = 0; i < MAX_CLIENTS; i++) {
		if (*the_network.client[i].nickname != 0) {
			the_network.client[i].disc_countdown--;
			if (the_network.client[i].disc_countdown <= 0) kick_client (i);
			else active_clients++;
		}
	}
	if ((active_clients < 1) && (main_state != STATE_LOBBY)) 
		change_state (STATE_LOBBY); // wait for new clients in lobby, not ingame
}

void network_cleanup ()
{
	if (shutdown(sockhdl, SHUT_RDWR) != 0) {
		log_fatal ("Failed to shutdown server socket.");
	}
	
	if (close(sockhdl) != 0) {
		log_fatal ("Failed to close server socket.");
	}
	
	connected = 0;
}

/* The following 4 functions are called when the corresponding message is received from the server.
  They go on to call the callback functions defined in network.h.
  Join callbacks are called after the join, leave callbacks BEFORE the leave. So the callbacks have
  access to the data (nicknames etc.) either way.
*/
void insert_client (char* nickname, int uid, struct sockaddr_storage* address, int addr_size)
{
	int i;
	StoCmessage message;
	memset (&message, 0, sizeof (message));
	
	log_debug ("insert_client (nickname %s, <address>, <addr_size>)",
	           nickname);
	
	for (i = 0; i < MAX_CLIENTS; i++) {
		if (*the_network.client[i].nickname == 0) {
			seq[i] = 1;
			seq_in[i] = 0;
			
			message.type = StoC_LOBBY_NEWCLIENT;
			message.clientidx = i;
			strcpy (message.nickname, nickname); 
			send_StoCmessage (&message, -1);
			
			strcpy (the_network.client[i].nickname, nickname);
			memcpy (&the_network.client[i].address, address, addr_size);
			the_network.client[i].uid = uid;
			the_network.client[i].disc_countdown = DISC_TIMELIMIT;
			
			message.type = StoC_ALLINFO;
			message.allinfo.state = main_state; // global game state
			memcpy (message.allinfo.client, the_network.client, sizeof(the_network.client));
			memcpy (message.allinfo.player, the_network.player, sizeof(the_network.player));
			memcpy (message.allinfo.owner,  the_network.owner,  sizeof(the_network.owner));
			send_StoCmessage (&message, i);
			
			if (notify_clientjoin != 0) notify_clientjoin (i);
			return;
		}
	}
}

void insert_player (int owneridx, char* nickname, int character)
{
	int i;
	StoCmessage message;
	memset (&message, 0, sizeof (message));
	
	log_debug ("insert_player (owner %d, nickname %s, character %d)", owneridx, nickname, character);
	
	for (i = 0; i < MAX_PLAYERS; i++) {
		if (!the_network.player[i].enabled) {
			message.type = StoC_LOBBY_NEWPLAYER;
			message.owner = owneridx;
			message.playeridx = i;
			strcpy (message.player.nickname, nickname);
			message.player.character_type = character;
			send_StoCmessage (&message, -1);
			
			the_network.owner[i] = owneridx;
			strcpy (the_network.player[i].nickname, nickname);
			the_network.player[i].character_type = character;
			if (notify_playerjoin != 0) notify_playerjoin (i);
			return;
		}
	}
	
	log_fatal ("Failed to insert player for client %d.", owneridx);
}

void remove_player (int index)
{
	StoCmessage message;
	memset (&message, 0, sizeof (message));

	log_debug ("remove_player (index %d)", index);
	
	if (notify_playerleave != 0) notify_playerleave (index);
	memset (&the_network.player[index], 0, sizeof (Character));
	
	message.type = StoC_LOBBY_PLAYERLEAVE;
	message.playeridx = index;
	send_StoCmessage (&message, -1);
}

/* ALL SEND AND RECEIVE METHODS transform the structs that are sent back and
  forth into a TEXT BASED human readable protocol and parse it. */

/* Does the same as send_StoCmessage but with explicitly specified seq # and no broadcast option. */
void send_StoCmessage_seq (StoCmessage* msg, int recipient, int* seq)
{
	if (*the_network.client[recipient].nickname == 0) return; // no client there

	char buffer [BUFSIZE];
	memset (buffer, 0, sizeof(buffer));
	int written = 0;
	
	switch (msg->type) {
	
	case StoC_PULSE:
		sprintf (buffer, "0 PULSE\n");
		break;
	
	case StoC_KICK:
		sprintf (buffer, "0 KICK\n");
		break;
	
	case StoC_NOTIFY:
		sprintf (buffer, "%d NOTIFY \"%s\"\n", *seq, msg->line);
		break;
	
	case StoC_CHAT:
		sprintf (buffer, "%d CHAT \"%s\"\n", *seq, msg->line);
		break;
	
	case StoC_ALLINFO:
		written = sprintf (buffer, "%d ALLINFO %d ", *seq, main_state);
		for (int i = 0; i < MAX_CLIENTS; i++) {
			if (*the_network.client[i].nickname != 0) {
				written += sprintf (buffer+written, "\"%s\" %d %d ",
				                    the_network.client[i].nickname,
				                    the_network.client[i].ready,
				                    the_network.client[i].uid);
			}
			else written += sprintf (buffer+written, "\"\" 0 ");
		}
		for (int i = 0; i < MAX_CLIENTS; i++) {
			if (*the_network.player[i].nickname != 0) {
				written += sprintf (buffer+written, "\"%s\" %d ",
				                    the_network.player[i].nickname,
				                    the_network.player[i].character_type);
			}
			else written += sprintf (buffer+written, "\"\" 0 ");
		}
		for (int i = 0; i < MAX_CLIENTS; i++) {
			written += sprintf (buffer+written, "%d ", the_network.owner[i]);
		}
		sprintf (buffer+written, "\n");
		break;
	
	case StoC_LOBBY_NEWCLIENT:
		sprintf (buffer, "%d LOBBY_NEWCLIENT %d \"%s\"\n",
		         *seq, msg->clientidx, msg->nickname);
		break;
	
	case StoC_LOBBY_NEWPLAYER:
		sprintf (buffer, "%d LOBBY_NEWPLAYER %d %d \"%s\" %d\n",
		         *seq, msg->owner, msg->playeridx, msg->player.nickname,
		         msg->player.character_type);
		break;
	
	case StoC_LOBBY_PLAYERLEAVE:
		sprintf (buffer, "%d LOBBY_PLAYERLEAVE %d\n", *seq, msg->playeridx);
		break;
	
	case StoC_LOBBY_CLIENTLEAVE:
		sprintf (buffer, "%d LOBBY_CLIENTLEAVE %d\n", *seq, msg->clientidx);
		break;
	
	case StoC_LOBBY_SEEDGAME:
		sprintf (buffer, "%d LOBBY_SEEDGAME %lu %d\n", *seq, msg->seed, msg->n_characters);
		break;
	
	case StoC_LOBBY_CLIENTREADY:
		sprintf (buffer, "%d LOBBY_CLIENTREADY %d\n", *seq, msg->clientidx);
		break;
	
	case StoC_LOBBY_CLIENTNOTREADY:
		sprintf (buffer, "%d LOBBY_CLIENTNOTREADY %d\n", *seq, msg->clientidx);
		break;
	
	case StoC_LOBBY_STARTGAME:
		sprintf (buffer, "%d LOBBY_STARTGAME\n", *seq);
		break;

	case StoC_INGAME_ACTION:
		written = sprintf (buffer, "%d INGAME_ACTION %d %d %d", *seq,
		                   msg->action.type, msg->action.actor,
		                   msg->action.start_time);
		
		switch (msg->action.type) { // append action type specific data
		
		case ACTION_TURN:
			sprintf (buffer+written, " %d\n", msg->action.turn->direction);
			break;
			
		case ACTION_ATTACK_CLOSE:
			sprintf (buffer+written, " %d\n", msg->action.cattack->weapon);
			break;
		
		case ACTION_ATTACK_RANGE:
			sprintf (buffer+written, " %d\n", msg->action.rattack->weapon);
			break;
		
		case ACTION_TRAVERSE:
			sprintf (buffer+written, " %d %d %d\n",
			         XYZ(msg->action.traverse->target));
			break;
		
		case ACTION_INTERACT:
			sprintf (buffer+written, " %d\n", msg->action.interact->target);
			break;
		
		default:
			sprintf (buffer+written, "\n");
			break;
		
		}
		break;

	case StoC_INGAME_DAMAGE:
		written = sprintf (buffer,
			"%d INGAME_DAMAGE %d %d %d %d %d %d %d %d %d\n", *seq,
			msg->damage.time,     msg->damage.where.x,
			msg->damage.where.y,  msg->damage.where.z,
			msg->damage.attacker, msg->damage.victim,
			msg->damage.weapon,   msg->damage.damage,
			msg->damage.hp);
		break;

	case StoC_INGAME_DIE:
		written = sprintf (buffer,
			"%d INGAME_DIE %d %d %d %d %d %d %d %d %d\n", *seq,
			msg->damage.time,     msg->damage.where.x,
			msg->damage.where.y,  msg->damage.where.z,
			msg->damage.attacker, msg->damage.victim,
			msg->damage.weapon,   msg->damage.damage,
			msg->damage.hp);
		break;
			
	case StoC_INGAME_SETCHARACTER:
		/* TODO: Need only player.skill and player.inventory ? */
		sprintf (buffer, "%d INGAME_SETCHARACTER %d %d %d %d %d %d %d %d\n",
		         *seq, msg->who, msg->level, msg->hp, msg->inventory.slot[0],
		         msg->inventory.slot[1], msg->inventory.slot[2],
		         msg->inventory.slot[3], msg->inventory.slot[4]);
		break;
	
	case StoC_INGAME_OVER:
		sprintf (buffer, "%d INGAME_OVER %d\n", *seq, msg->winner);
		break;
	
	default:
		log_fatal ("Failed to send_StoCmessage of unknown type %d.", msg->type);
		break;
	
	}
	
	// decide whether to increment seq
	switch (msg->type) {
		case StoC_PULSE:
		case StoC_KICK:
			break;
		default:
			memcpy (&msglog[recipient][ (*seq) % MSGLOG_SIZE ],
			        msg, sizeof(StoCmessage)); // store message
			(*seq)++;
			break;
	}
	
	#if TOWERHACK_DEBUG_NETWORK
	if (msg->type != StoC_PULSE) {
		log_debug ("SEND to %d: %s", recipient, buffer);
	}
	#endif
	
	int error = sendto (sockhdl, buffer, sizeof(buffer), 0,
	                    (struct sockaddr* ) &the_network.client[recipient].address,
	                    sizeof(struct sockaddr_storage));
	if (error != sizeof(buffer)) {
		log_fatal ("Error sending data to client %d.", recipient);
	}
}

/* Send server to client message. Recipient == -1 -> broadcast to all clients. */
void send_StoCmessage (StoCmessage* msg, int recipient)
{
	if (recipient == -1) { // broadcast
		for (int i = 0; i < MAX_CLIENTS; i++) send_StoCmessage (msg, i);
	} else { // address only 1 client
		/* test REPEAT packets
	#if TOWERHACK_DEBUG
		if ((msg->type != StoC_PULSE) && ((seq[recipient] % 50) == 49)) {
			log_debug ("Dropping packet %d on purpose", seq[recipient]);
			memcpy (&msglog[recipient][ seq[recipient] % MSGLOG_SIZE ], msg, sizeof(StoCmessage)); // store message
			seq[recipient]++;
			return;
		}
	#endif */
		send_StoCmessage_seq (msg, recipient, &seq[recipient]);
	}
}

CtoSmsgType parsetype (char** token)
{
	char* typestr = *token;
	while (isspace(*typestr)) typestr++;
	
	#define TRYPARSE(STR) if (strncmp(typestr, #STR, sizeof(#STR)-1) == 0) { *token = typestr + sizeof(#STR)-1; return CtoS_##STR; }
	
	TRYPARSE(PULSE) else
	TRYPARSE(HELLO) else
	TRYPARSE(REPEAT) else
	TRYPARSE(CHAT) else
	TRYPARSE(LOBBY_ADDPLAYER) else
	TRYPARSE(LOBBY_SETPLAYERDATA) else
	TRYPARSE(LOBBY_REMOVEPLAYER) else
	TRYPARSE(LOBBY_READY) else
	TRYPARSE(LOBBY_NOTREADY) else
	TRYPARSE(INGAME_ACTION) else
	TRYPARSE(INGAME_SETINVENTORY) else
		log_debug ("Failed to parsetype \"%s\".\n", typestr);
	
	return CtoS_ILLEGALMSG;
}

int client_by_uid (int uid)
{
	int i;
	for (i = 0; i < MAX_CLIENTS; i++)
		if (the_network.client[i].uid == uid) return i;
	
	return -1;
}

void send_pulse (int rcpt)
{
	StoCmessage message;
	memset (&message, 0, sizeof (message));

	message.type = StoC_PULSE;
	send_StoCmessage (&message, rcpt);
}

/* Receive client to server message */
void receive_CtoSmessage (CtoSmessage* msg, int* sender)
{
	char buffer [BUFSIZE];
	char* readptr = buffer;
	fd_set set;
	struct timeval timeout;
	int sseq;
	int error;
	struct sockaddr_storage source;
	socklen_t source_size = sizeof(source);
	int parsed_value = 0;
	
tryagain:
	readptr = buffer;
	memset (buffer, 0, sizeof(buffer));
	memset (&timeout, 0, sizeof(timeout));
	FD_ZERO (&set);
	FD_SET (sockhdl, &set);
	
	error = select(sockhdl+1, &set, 0, 0, &timeout);
	if (error < 0) {
		log_fatal ("Error on select socket.");
	}
	
	if (!FD_ISSET(sockhdl, &set)) {
		msg->type = CtoS_NOMSG;
		return;
	}

	error = recvfrom(sockhdl, buffer, sizeof(buffer), 0,
	                 (struct sockaddr*) &source, &source_size);
	if (error != sizeof(buffer)) {
		log_fatal ("Error on receive from socket.");
	}
	
	common_network_parse_int (&msg->uid, &readptr); // FIXME: error handling
	*sender = client_by_uid (msg->uid);
	
	common_network_parse_int (&sseq, &readptr); // FIXME: error handling
	msg->type = parsetype (&readptr);
	
	#if TOWERHACK_DEBUG_NETWORK
	if (msg->type != CtoS_PULSE) {
		log_debug ("RECEIVE from %d: %s", *sender, buffer);
	}
	#endif
	
	if ((sseq != 0) && (sender >= 0)) {
		if (sseq < seq_in[*sender]+1) {
			log_info ("seq out of order, expected %d, got %d", seq_in[*sender]+1, sseq);
			goto tryagain;
		}
		seq_in[*sender] = sseq;//+1;
	}
	
	if ((*sender == -1) && (msg->type != CtoS_HELLO)) {
		sendto (sockhdl, "0 KICK\n", sizeof("0 KICK\n"), 0, (struct sockaddr*) &source, source_size); 
		log_info ("Rejected message from unknown client uid %d.", msg->uid);
		goto tryagain;
	}
	
	// do not consider message type for this
	the_network.client[*sender].disc_countdown = DISC_TIMELIMIT;
	
	int type, actor, start_time, dir, x, y, z, index;
	
	switch (msg->type) {
	
	case CtoS_PULSE:
		send_pulse (*sender);
		goto tryagain;
		
	case CtoS_HELLO:
		common_network_parse_string (msg->nickname, &readptr); // FIXME: error handling
		insert_client (msg->nickname, msg->uid, &source, source_size);
		goto tryagain;
	
	case CtoS_REPEAT:
		common_network_parse_int (&msg->seq, &readptr); // FIXME: error handling
		for (int i = 0; i < MAX_REPEAT_AT_ONCE; i++) {
			if ((msg->seq % MSGLOG_SIZE) == (seq[*sender] % MSGLOG_SIZE)) break;
			send_StoCmessage_seq (&msglog [*sender][ msg->seq % MSGLOG_SIZE ], *sender, &msg->seq);
		}
		goto tryagain;
	
	case CtoS_CHAT:  /* (char* line) contains chat message to be sent to everyone */
		common_network_parse_string (msg->line, &readptr); // FIXME: error handling
		break;
	
	case CtoS_LOBBY_ADDPLAYER:
		common_network_parse_string (msg->nickname, &readptr); // FIXME: error handling
		common_network_parse_int (&parsed_value, &readptr); // FIXME: error handling
		msg->data.character_type = parsed_value;
		insert_player (*sender, msg->nickname, msg->data.character_type);
		goto tryagain;
	
	case CtoS_LOBBY_SETPLAYERDATA:
		common_network_parse_int (&msg->slot, &readptr); // FIXME: error handling
		common_network_parse_string (msg->nickname, &readptr); // FIXME: error handling
		common_network_parse_int (&parsed_value, &readptr); // FIXME: error handling
		msg->data.character_type = parsed_value;
		break;
	
	case CtoS_LOBBY_REMOVEPLAYER:
		common_network_parse_int (&msg->slot, &readptr); // FIXME: error handling
		remove_player (msg->slot);
		goto tryagain;
	
	case CtoS_LOBBY_READY:
		break;
	
	case CtoS_LOBBY_NOTREADY:
		break;
	
	case CtoS_INGAME_ACTION:
		common_network_parse_int (&type, &readptr); // FIXME: error handling
		common_network_parse_int (&actor, &readptr); // FIXME: error handling
		common_network_parse_int (&start_time, &readptr); // FIXME: error handling
		
		int weapon;
		switch (type) {

		case ACTION_TURN:
			common_network_parse_int (&dir, &readptr); // FIXME: error handling
			msg->action = action_alloc_turn (actor, start_time, dir);
			break;
			
		case ACTION_ATTACK_CLOSE:
			common_network_parse_int (&weapon, &readptr); // FIXME: error handling
			msg->action = action_alloc_closeattack (actor, start_time, weapon);
			break;
		
		case ACTION_ATTACK_RANGE:
			common_network_parse_int (&weapon, &readptr); // FIXME: error handling
			msg->action = action_alloc_rangeattack (actor, start_time, weapon);
			break;
			
		case ACTION_TRAVERSE:
			common_network_parse_int (&x, &readptr); // FIXME: error handling
			common_network_parse_int (&y, &readptr); // FIXME: error handling
			common_network_parse_int (&z, &readptr); // FIXME: error handling
			msg->action = action_alloc_traverse (actor, start_time,
			                                     (Coord) {x,y,z});
			break;
			
		case ACTION_INTERACT:
			common_network_parse_int (&index, &readptr); // FIXME: error handling
			msg->action = action_alloc_interact (actor, start_time, index);
			break;
			
		default:
			msg->action = NO_ACTION (actor);
			break;
		
		}
		
		msg->action.type = type;
		msg->action.actor = actor;
		msg->action.start_time = start_time;
		break;
		
	case CtoS_INGAME_SETINVENTORY:
		common_network_parse_int (&msg->character, &readptr); // FIXME: error handling
		common_network_parse_int (&msg->inventory.slot[0], &readptr); // FIXME: error handling
		common_network_parse_int (&msg->inventory.slot[1], &readptr); // FIXME: error handling
		common_network_parse_int (&msg->inventory.slot[2], &readptr); // FIXME: error handling
		common_network_parse_int (&msg->inventory.slot[3], &readptr); // FIXME: error handling
		common_network_parse_int (&msg->inventory.slot[4], &readptr); // FIXME: error handling
		break;
	
	default:
		log_error ("Received unknown message type %d.", msg->type);
		break;
	
	}
}

